import {
  join,
  normalize,
  parseJson,
  Path,
  strings,
} from '@angular-devkit/core';
import {
  apply,
  branchAndMerge,
  chain,
  mergeWith,
  move,
  Rule,
  SchematicsException,
  Source,
  template,
  Tree,
  url,
} from '@angular-devkit/schematics';
import * as fse from 'fs-extra';
import { removeTemplateExt } from '../../utils/source-root.helpers';
import {
  DEFAULT_DIR_ENTRY_APP,
  DEFAULT_LANGUAGE,
  DEFAULT_PACKAGES_PATH,
} from '../defaults';
import { SubModuleOptions } from './sub-module.schema';

type UpdateJsonFn<T> = (obj: T) => T | void;

interface CpaasPackageInfoItem {
  name: string;
}

interface CpaasConfigPartialType {
  packages: CpaasPackageInfoItem[];
}

export function main(options: SubModuleOptions): Rule {
  // const appName = getAppNameFromPackageJson();
  options = transform(options);
  options.packageName = strings.dasherize(`${options.name}`);
  return chain([
    updateCpaasConfig(options, options.packageName),
    branchAndMerge(mergeWith(generate(options))),
  ]);
}

function getAppNameFromPackageJson(): string {
  try {
    if (!fse.existsSync('./package.json')) {
      return DEFAULT_DIR_ENTRY_APP;
    }
    const packageJson = fse.readJsonSync('./package.json');
    if (!packageJson.name) {
      return DEFAULT_DIR_ENTRY_APP;
    }
    let name = packageJson.name;
    name = name.replace(/[^\w.]+/g, '-').replace(/\-+/g, '-');
    return name[0] === '-' ? name.substr(1) : name;
  } catch {
    return DEFAULT_DIR_ENTRY_APP;
  }
}

function transform(options: SubModuleOptions): SubModuleOptions {
  const target: SubModuleOptions = Object.assign({}, options);
  const defaultSourceRoot =
    options.rootDir !== undefined ? options.rootDir : DEFAULT_PACKAGES_PATH;

  if (!target.name) {
    throw new SchematicsException('Option (name) is required.');
  }
  target.language = !!target.language ? target.language : DEFAULT_LANGUAGE;
  target.name = strings.dasherize(target.name);
  target.path =
    target.path !== undefined
      ? join(normalize(defaultSourceRoot), target.path)
      : normalize(defaultSourceRoot);

  return target;
}

function updateJsonFile<T>(
  host: Tree,
  path: string,
  callback: UpdateJsonFn<T>
): Tree {
  const source = host.read(path);
  if (source) {
    const sourceText = source.toString('utf-8');
    const json = parseJson(sourceText);
    callback((json as {}) as T);
    host.overwrite(path, JSON.stringify(json, null, 2));
  }
  return host;
}

function updateCpaasConfig(options: SubModuleOptions, defaultAppName: string) {
  return (host: Tree) => {
    if (!host.exists('.cpaasrc.json')) {
      host.create('.cpaasrc.json', '{ "packages": [] }');
    }
    return updateJsonFile(
      host,
      '.cpaasrc.json',
      (cpaasrc: CpaasConfigPartialType) => {
        if (
          cpaasrc.packages &&
          cpaasrc.packages.find(item => item.name === options.packageName)
        ) {
          return cpaasrc;
        }
        cpaasrc.packages = [
          {
            name: options.packageName,
          },
          ...(cpaasrc.packages || []),
        ];
        return cpaasrc;
      }
    );
  };
}

function updatePackageJson(options: SubModuleOptions, defaultAppName: string) {
  return (host: Tree) => {
    if (!host.exists('package.json')) {
      return host;
    }
    return updateJsonFile(
      host,
      'package.json',
      (packageJson: Record<string, any>) => {
        // updateNpmScripts(packageJson.scripts, options, defaultAppName);
        updateDependencies(packageJson.dependencies, options, defaultAppName);
        // updateJestOptions(packageJson.jest, options);
      }
    );
  };
}

function updateDependencies(
  scripts: Record<string, any>,
  options: SubModuleOptions,
  defaultAppName: string
) {
  scripts[options.packageName] = '0.0.1';
}

function generate(options: SubModuleOptions): Source {
  const path = join(options.path as Path, options.packageName);

  return apply(url(join('./files' as Path, options.language)), [
    template({
      ...strings,
      ...options,
    }),
    move(path),
    removeTemplateExt(),
  ]);
}
